use crate::dbs::sqlite::Peer;
use async_graphql::{Context, ID};
use derivative::Derivative;
use rbatis::rbatis::Rbatis;
use serde::{Deserialize, Serialize};
use serde_json::Result;

use crate::features::gfilter::{
  self, DateTimeFilterIn, IntFilterIn, StringExactFilterIn, StringPatternFilterIn,
};
use crate::features::gnode::KV;
use crate::features::quizzes::modelsdbo::{QuizDBO, QuizItemDBO};
use crate::features::quizzes::repository;
use crate::features::quizzes::services;

use crate::util::markdown;

#[derive(async_graphql::SimpleObject, Serialize, Deserialize, Clone)]
#[graphql(complex)]
#[derive(Derivative)]
#[derivative(Default)]
/// this is Quiz
pub struct Quiz {
  /// this is api document
  pub id: ID,
  pub seq: u32,
  pub level: Option<String>,
  pub subject: String,
  pub m3u8: Option<String>,
  pub title: Option<String>,
  pub description: Option<String>,

  /// local created timestamp, seconds since 1970-01-01
  pub created_at: i64,
  /// local updated timestamp, seconds since 1970-01-01
  pub updated_at: i64,

  pub status: Option<String>,
}

#[derive(async_graphql::SimpleObject, Serialize, Deserialize, Clone, Derivative)]
#[derivative(Default)]
/// this is QuizEbpInfo
pub struct QuizEbpInfo {
  /// this is api document
  pub user_id: ID,
  pub quiz_id: ID,
  pub ebp_at: Option<i64>,
}

#[async_graphql::ComplexObject]
impl Quiz {
  pub async fn from(&self, ctx: &Context<'_>) -> String {
    let mut from = String::new();
    from.push_str(format!("{}", &self.seq).as_str());
    from.push_str("<");
    from.push_str(&self.title.clone().unwrap_or_default());
    from.push_str(">");

    from
  }

  pub async fn description_markdown(&self) -> Option<String> {
    match &self.description {
      Some(ref v) => {
        let v_str = v.as_str();
        Some(markdown::parse(v_str))
      }
      _ => None,
    }
  }

  /// get quizitem member of the quiz
  pub async fn quiz_items(&self, ctx: &Context<'_>) -> Vec<QuizItem> {
    let rbatis_pool = ctx.data_unchecked::<Rbatis>();
    let id = &self.id.to_owned();
    let qi_dbos = services::get_quizitem_members_of(rbatis_pool, id).await;

    qi_dbos.into_iter().map(|x| x.into()).collect()
  }

  pub async fn my_ebp_info(&self, ctx: &Context<'_>) -> Option<QuizEbpInfo> {
    let rbatis_pool = ctx.data_unchecked::<Rbatis>();
    let my_peer = ctx.data_unchecked::<Peer>();

    let edge_dbo =
      services::find_user_quiz_ebp_info(rbatis_pool, &self.id.0, &my_peer.login_user_id).await;
    if edge_dbo.is_ok() {
      let edge_dbo = edge_dbo.unwrap();
      Some(QuizEbpInfo {
        user_id: ID(edge_dbo.subject),
        quiz_id: ID(edge_dbo.object),
        ebp_at: edge_dbo.updated_at,
      })
    } else {
      None
    }
  }
}

impl From<QuizDBO> for Quiz {
  fn from(item: QuizDBO) -> Self {
    Quiz {
      id: ID::from(item.id),
      seq: item.seq.unwrap(),
      level: item.level,
      subject: item.subject,
      title: item.title,
      m3u8: item.m3u8,
      description: item.description,
      created_at: item.created_at,
      updated_at: item.updated_at,
      status: item.status,
    }
  }
}

#[derive(async_graphql::InputObject, Serialize, Deserialize, Clone, Debug)]
pub struct NewQuiz {
  pub id: String,
  pub seq: u32,
  pub level: String,
  pub subject: String,
  pub m3u8: String,
  pub title: String,
  pub description: String,
  pub status: String,
}

#[derive(async_graphql::InputObject, Serialize, Deserialize, Clone, Debug)]
pub struct AddEBPQuizIn {
  pub id: ID,
}

#[derive(async_graphql::SimpleObject, Serialize, Deserialize, Clone)]
#[graphql(complex)]
#[derive(Derivative)]
#[derivative(Default)]
/// this is QuizItem
pub struct QuizItem {
  pub id: ID,
  pub level: Option<String>,
  pub subject: Option<String>,
  pub title: Option<String>,
  pub options: Option<Vec<KV>>,
  pub opstype: Option<String>,
  /// standard answer
  pub std_answer: Option<String>,
  pub content_id: Option<String>,
  pub description: Option<String>,
  pub m3u8: Option<String>,
  pub is_master: Option<bool>,
  pub master_id: Option<String>,
  /// local created timestamp, seconds since 1970-01-01
  pub created_at: i64,
  /// local updated timestamp, seconds since 1970-01-01
  pub updated_at: i64,
  pub status: Option<String>,
}

#[derive(async_graphql::SimpleObject, Serialize, Deserialize, Clone)]
/// Single Choice Answer
pub struct QuizAnswerRadio {
  /// 对应单选key, see struct KV
  pub option_key: String,
}

#[derive(async_graphql::SimpleObject, Serialize, Deserialize, Clone)]
/// Multiple Choice Answer
pub struct QuizAnswerCheck {
  /// 对应多选keys, see struct KV
  pub option_keys: Vec<String>,
}

#[derive(async_graphql::SimpleObject, Serialize, Deserialize, Clone)]
/// Free Answer, see table quizanswers
pub struct QuizAnswerFree {
  pub id: ID,
  /// 回答标题
  pub title: Option<String>,
  /// 回答内容
  pub content: Option<String>,
}

impl From<QuizItemDBO> for QuizItem {
  fn from(item: QuizItemDBO) -> Self {
    let mut options: Vec<KV> = vec![];

    if item.options.is_some() {
      let r = serde_json::from_str::<Vec<KV>>(&item.options.to_owned().unwrap());
      if r.is_ok() {
        options = r.unwrap();
      } else {
        println!("serde bad item.options={:?}", item.options.to_owned());
      }
    }

    QuizItem {
      id: ID::from(item.id),
      level: item.level,
      subject: item.subject,
      title: item.title,
      options: Some(options),
      opstype: item.opstype,
      std_answer: item.std_answer,
      content_id: item.content_id,
      description: item.description,
      m3u8: item.m3u8,
      is_master: match item.is_master {
        Some(v) => {
          if v == 1 {
            Some(true)
          } else {
            Some(false)
          }
        }
        _ => Some(false),
      },
      master_id: item.master_id,
      created_at: item.created_at,
      updated_at: item.updated_at,
      status: item.status,
    }
  }
}

#[async_graphql::ComplexObject]
impl QuizItem {
  pub async fn from(&self) -> String {
    let mut from = String::new();
    from.push_str(&self.id);
    from.push_str("<");
    from.push_str(&self.title.clone().unwrap_or_default());
    from.push_str(">");

    from
  }

  pub async fn description_markdown(&self) -> Option<String> {
    match &self.description {
      Some(ref v) => {
        let v_str = v.as_str();
        Some(markdown::parse(v_str))
      }
      _ => None,
    }
  }

  pub async fn master(&self, ctx: &Context<'_>) -> Option<QuizItem> {
    let rbatis_pool = ctx.data_unchecked::<Rbatis>();
    let my_peer = ctx.data_unchecked::<Peer>();
    let master_id = &self.master_id.to_owned();
    let res = match master_id {
      Some(v) => {
        let db_res = repository::get_quiz_item_by_id(rbatis_pool, v).await;
        match db_res {
          Ok(dbo) => Some(dbo.into()),
          _ => None,
        }
      }
      _ => None,
    };
    res
  }

  pub async fn sub_quiz_items(&self, ctx: &Context<'_>) -> Option<Vec<QuizItem>> {
    let rbatis_pool = ctx.data_unchecked::<Rbatis>();
    let my_peer = ctx.data_unchecked::<Peer>();
    let db_res = repository::get_quiz_item_by_master_id(rbatis_pool, &self.id.0).await;
    let mut r = None;
    if db_res.is_ok() {
      r = match db_res.unwrap() {
        Some(v) => {
          let qi_gqos: Vec<QuizItem> = v.into_iter().map(|x| x.into()).collect();

          Some(qi_gqos)
        }
        _ => None,
      };
    }
    r
  }
}

#[derive(async_graphql::InputObject, Serialize, Deserialize, Clone, Debug)]
pub struct NewQuizItem {
  pub id: String,
  pub level: String,
  pub subject: String,
  pub title: String,
  pub description: String,
  pub options: String,
  pub answer: String,
  pub content_id: String,
  pub status: String,
}

#[derive(async_graphql::InputObject, Serialize, Deserialize, Clone, Debug)]
pub struct QuizFilterIn {
  /// default condition fields logic relation, AND the provided conditions
  pub and: Option<Vec<QuizFilterIn>>,

  /// or condition fields logic relation, OR the provided conditions
  pub or: Option<Vec<QuizFilterIn>>,

  /// not condition fields logic relation, not the provided condition
  pub not: Box<Option<QuizFilterIn>>,

  pub subject: Option<StringExactFilterIn>,
  pub title: Option<StringPatternFilterIn>,
  pub description: Option<StringPatternFilterIn>,

  pub created_at: Option<DateTimeFilterIn>,
  pub updated_at: Option<DateTimeFilterIn>,
  pub status: Option<StringExactFilterIn>,
}
